#pragma once
#include <functional>
#include "RenderingPrefix.h"
#include "RenderResource.h"
#include "Runtime/Utils/Data.h"

namespace Alice
{
    class FUniformLightMapPolicy
    {
    public:
	    const ELightMapPolicyType mIndirectPolicy;
        FUniformLightMapPolicy(ELightMapPolicyType InIndirectPolicy) : mIndirectPolicy(InIndirectPolicy) {}
        ELightMapPolicyType GetIndirectPolicy() const { return mIndirectPolicy; }
    };
    template <ELightMapPolicyType Policy>
    class TUniformLightMapPolicy : public FUniformLightMapPolicy
    {
    public:
        TUniformLightMapPolicy() : FUniformLightMapPolicy(Policy) {}
    };
    class MaterialShared;
    class FShaderMapResource;
    class FShaderMapResourceCode;
    /**
     * An object which is used to serialize/deserialize, compile, and cache a particular shader class.
     *
     * A shader type can manage multiple instance of FShader across mutiple dimensions such as EShaderPlatform, or permutation id.
     * The number of permutation of a shader type is simply given by GetPermutationCount().
     */
    class FShaderType
    {
    public:
        enum class EShaderTypeForDynamicCast : uint32
        {
            Global,
            Material,
            MeshMaterial,
            Niagara,
            OCIO,
            ComputeKernel,
            NumShaderTypes,
        };
        EShaderTypeForDynamicCast ShaderTypeForDynamicCast;
        const TCHAR* SourceFilename;
        FShaderType(
            EShaderTypeForDynamicCast InShaderTypeForDynamicCast,
            const TCHAR* InSourceFilename
        ):ShaderTypeForDynamicCast(InShaderTypeForDynamicCast)
        ,SourceFilename(InSourceFilename)
        {
            
        }
    };
    /**
     * A shader meta type for material-linked shaders.
     */
    class FMaterialShaderType : public FShaderType
    {
    public:
        FMaterialShaderType(const TCHAR* InSourceFilename):FShaderType(EShaderTypeForDynamicCast::Material,InSourceFilename)
        {
        }
    };
    /**
     * A shader meta type for material-linked shaders which use a vertex factory.
     */
    class FMeshMaterialShaderType : public FShaderType
    {
    public:
        FMeshMaterialShaderType(const TCHAR* InSourceFilename):FShaderType(EShaderTypeForDynamicCast::MeshMaterial,InSourceFilename)
        {
        }
    };
    /**
     * Registers a shader type in various systems. Should be created as a static field/global.
     * 
     * Each shader type is collected here, not as an instance but as an accessor, so the actual construction can be deferred.
     * The collection happens during static init, the actual construction happens later during launch.
     * The purpose of collecting the types is the CommitAll function, called in LaunchEngineLoop, to ensure all type instances are constructed and registered before other systems start iterating them.
     */
    class FShaderTypeRegistration
    {
    public:
        FShaderTypeRegistration(std::function<FShaderType&()> inLazyShaderTypeAccessor)
            : mLazyShaderTypeAccessor(inLazyShaderTypeAccessor)
        {
            GetInstances().push_back(this);
        }

        static std::vector<const FShaderTypeRegistration*>& GetInstances();

        // Actually register all the types and clear the array
        static void CommitAll();
        
    private:
        std::function<FShaderType&()> mLazyShaderTypeAccessor;
    };
    class Shader : public RenderResource
    {
    public:
        EShaderFrequency mShaderFrequency;
        EMeshPass::Type mMeshPass;
        CGIShader* mCGIShader;
        Data* mShaderCode;
        static std::unordered_map<std::string,Shader*> mCachedShaders;
        Shader(EShaderFrequency inShaderFrequency,EMeshPass::Type inMeshPass);
        template<typename T>
        static T*GetOrCreateShaderFromShaderPath(EMeshPass::Type inMeshPass,const char* inShaderPath);
    };
    class VertexShader:public Shader
    {
    public:
        VertexShader(EMeshPass::Type inMeshPass,const char * inShaderPath);
    };
    class PixelShader:public Shader
    {
    public:
        PixelShader(EMeshPass::Type inMeshPass,const char * inShaderPath);
    };
    class MaterialShader:public Shader
    {
    public:
        using ShaderMetaType = FMaterialShaderType;
    };
    class MeshMaterialShader:public MaterialShader
    {
    public:
        using ShaderMetaType=FMeshMaterialShaderType;
    };
    class FShaderPipelineType
    {
    public:
    };
    class ShaderPipeline
    {
    public:
        Shader* mShaders[SF_NumGraphicsFrequencies];
        void SetShader(EShaderFrequency inShaderFrequency,Shader*inShader);
    };
    class FShaderMapContent
    {
    public:
    };
    class FMaterialShaderMapContent : public FShaderMapContent
    {
    public:
    };
    class FMeshMaterialShaderMapContent : public FShaderMapContent
    {
    public:
    };
    class FShaderMapBase
    {
    public:
        FShaderMapResource* mShaderMapResource;
        FShaderMapResourceCode * mShaderMapResourceCode;
        
        void InitResource();
        void FinalizeContent();
    };
    template<typename ContentType>
    class TShaderMap : public FShaderMapBase
    {
    public:
        void FinalizeContent()
        {
            FShaderMapBase::FinalizeContent();
        }
    };
    class FMaterialShaderMap : public TShaderMap<FMaterialShaderMapContent>
    {
    public:
        void Compile(MaterialShared* inMaterial);
    };
    template<typename ShaderType>
    class TShaderRefBase
    {
    public:
        ShaderType* ShaderContent;
        const FShaderMapBase* ShaderMap;
        TShaderRefBase() : ShaderContent(nullptr), ShaderMap(nullptr) {}
	    TShaderRefBase(ShaderType* InShader, const FShaderMapBase& InShaderMap) : ShaderContent(InShader), ShaderMap(&InShaderMap) { checkSlow(InShader); }
        inline bool IsValid() const { return ShaderContent != nullptr; }
    };
    template<typename ShaderType>
    using TShaderRef = TShaderRefBase<ShaderType>;
    
    class TBasePassVS : public MeshMaterialShader
    {
    public:
        using ShaderMetaType=FMeshMaterialShaderType;
        using ShaderMapContentType=FMeshMaterialShaderMapContent;
        static ShaderMetaType& GetStaticType();
    private:
        static FShaderTypeRegistration ShaderTypeRegistration;
    };
    class TBasePassPS : public MeshMaterialShader
    {
    public:
        using ShaderMetaType=FMeshMaterialShaderType;
        using ShaderMapContentType=FMeshMaterialShaderMapContent;
        static ShaderMetaType& GetStaticType();
    private:
        static FShaderTypeRegistration ShaderTypeRegistration;
    };
}
